package org.fluxtream.connectors.google_latitude; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.fluxtream.core.aspects.FlxLogger; import org.fluxtream.core.connectors.Connector.UpdateStrategyType; import org.fluxtream.core.connectors.FileUploadSupport; import org.fluxtream.core.connectors.annotations.Updater; import org.fluxtream.core.connectors.location.LocationFacet; import org.fluxtream.core.connectors.updaters.AbstractUpdater; import org.fluxtream.core.connectors.updaters.UpdateInfo; import org.fluxtream.core.domain.ApiKey; import org.fluxtream.core.domain.Notification; import org.fluxtream.core.services.ApiDataService; import org.codehaus.jackson.JsonFactory; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonParser; import org.codehaus.jackson.JsonToken; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component @Updater(prettyName = "Latitude", value = 2, objectTypes = { LocationFacet.class }, updateStrategyType = UpdateStrategyType.INCREMENTAL) public class GoogleLatitudeUpdater extends AbstractUpdater implements FileUploadSupport { FlxLogger logger = FlxLogger.getLogger(GoogleLatitudeUpdater.class); @Autowired ApiDataService apiDataService; public GoogleLatitudeUpdater() { super(); } @Override public void updateConnectorDataHistory(UpdateInfo updateInfo) throws Exception { if (guestService.getApiKeyAttribute(updateInfo.apiKey, "googleConsumerKey")!=null) { sendServiceDiscontinuedWarning(updateInfo); cleanupOldTokens(updateInfo); } } public void updateConnectorData(UpdateInfo updateInfo) throws Exception { if (guestService.getApiKeyAttribute(updateInfo.apiKey, "googleConsumerKey")!=null) { sendServiceDiscontinuedWarning(updateInfo); cleanupOldTokens(updateInfo); } } @Override public void setDefaultChannelStyles(ApiKey apiKey) {} private void sendServiceDiscontinuedWarning(final UpdateInfo updateInfo) { notificationsService.addNamedNotification(updateInfo.getGuestId(), Notification.Type.WARNING, connector().statusNotificationName(), "Heads Up. Google recently discontinued support for their Latitude service.<br>" + "However, Google Takeout will let you get a backup of your data that you will be able to import in Fluxtream.<br>" + "If you choose to do this, please head to <a href=\"javascript:App.manageConnectors()\">Manage Connectors</a>,<br>" + "go to the Google Latitude connector section and click on the upload icon <i class=\"icon-arrow-up\"></i>," + "To track your location, we now recommend using the <a target=\"_blank\" href\"http://movesapp.com/\">Moves App<a>."); } private void cleanupOldTokens(final UpdateInfo updateInfo) { guestService.removeApiKeyAttribute(updateInfo.apiKey.getId(), "googleConsumerKey"); if (guestService.getApiKeyAttribute(updateInfo.apiKey, "googleConsumerSecret")!=null) guestService.removeApiKeyAttribute(updateInfo.apiKey.getId(), "googleConsumerSecret"); } @Override public int importFile(final ApiKey apiKey, final File f) throws Exception { try { ZipFile zipFile = new ZipFile(f); final Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { final ZipEntry zipEntry = entries.nextElement(); if (zipEntry.isDirectory()) continue; if (zipEntry.getName().endsWith("LocationHistory.json")) return parseLocations(apiKey, zipFile.getInputStream(zipEntry)); } throw new RuntimeException("Couldn't find LocationHistory.json in the uploaded zip file"); } catch (Exception e) { notificationsService.addNamedNotification(apiKey.getGuestId(), Notification.Type.WARNING, connector().statusNotificationName(), "Failed to import Google Latitude zip file, error is:<br>" + e.getMessage()); throw (e); } } private int parseLocations(final ApiKey apiKey, final InputStream inputStream) throws IOException { ObjectMapper mapper = new ObjectMapper(); JsonFactory jfactory = mapper.getJsonFactory(); JsonParser jParser = jfactory.createJsonParser(inputStream); JsonToken token; List<LocationFacet> locations = new ArrayList<LocationFacet>(); getToLocationData(jParser); int nLocations = 0; while ((token = jParser.nextToken())!=null) { if (token==JsonToken.START_OBJECT) { jParser.nextToken(); parseLocation(apiKey, jParser, locations); if (locations.size()==1000) { nLocations += 1000; boolean connectorIsLive = persistLocations(apiKey, locations); if (!connectorIsLive) break; locations.clear(); } } } nLocations += locations.size(); if (apiKey!=null) persistLocations(apiKey, locations); return nLocations; } private boolean persistLocations(ApiKey apiKey, List<LocationFacet> locations) { if (guestService.getApiKey(apiKey.getId())!=null){ apiDataService.addGuestLocations(apiKey.getGuestId(), locations); return true; } else { return false; } } private void getToLocationData(final JsonParser jParser) throws IOException { // The start of the LocationHistory.json file looks like this: // { // "somePointsHidden" : true, // "locations" : [ { // "timestampMs" : "1380841104348", // Get to the first open brace while (jParser.nextToken()!=JsonToken.START_OBJECT); // Go forward until currentName is set while (jParser.getCurrentName()==null) jParser.nextToken(); // Go forward until we reach the "locations" array while (!jParser.getCurrentName().equals("locations")) jParser.nextToken(); } void parseLocation(final ApiKey apiKey, final JsonParser jParser, final List<LocationFacet> locations) throws IOException { LocationFacet locationFacet = new LocationFacet(); if (apiKey!=null) { locationFacet.apiKeyId = apiKey.getId(); locationFacet.guestId = apiKey.getGuestId(); } locationFacet.timeUpdated = System.currentTimeMillis(); locationFacet.source = LocationFacet.Source.GOOGLE_LATITUDE; do { String fieldName = jParser.getCurrentName(); final JsonToken jsonToken = jParser.nextValue(); if (jsonToken.isScalarValue()) { if (fieldName.equals("timestampMs")) { long ts = Long.valueOf(jParser.getText()); locationFacet.timestampMs = ts; locationFacet.start = ts; locationFacet.end = ts; locationFacet.api = 2; } else if (fieldName.equals("accuracy")) { int accuracy = jParser.getIntValue(); locationFacet.accuracy = accuracy; } else if (fieldName.equals("altitude")) { int altitude = jParser.getIntValue(); locationFacet.altitude = altitude; } else if (fieldName.equals("heading")) { int heading = jParser.getIntValue(); locationFacet.heading = heading; } else if (fieldName.equals("latitudeE7")) { int lat = jParser.getIntValue(); locationFacet.latitude = lat/1E7f; } else if (fieldName.equals("longitudeE7")) { int lon = jParser.getIntValue(); locationFacet.longitude = lon/1E7f; } else if (fieldName.equals("velocity")) { int speed = jParser.getIntValue(); locationFacet.speed = speed; } } else { final JsonNode junk = jParser.readValueAsTree(); logger.info("we are not interested in this junk: " + junk.toString()); } } while (jParser.nextToken() != JsonToken.END_OBJECT); locations.add(locationFacet); } }